package org.andengine.util.adt.list;

import org.andengine.util.IMatcher;
import org.andengine.util.call.ParameterCallable;

import java.util.ArrayList;
import java.util.List;

/**
 * (c) 2010 Nicolas Gramlich
 * (c) 2011 Zynga Inc.
 *
 * @author Nicolas Gramlich
 * @since 22:20:08 - 27.12.2010
 */
public class SmartList<T> extends ArrayList<T> {
    // ===========================================================
    // Constants
    // ===========================================================

    private static final long serialVersionUID = 8655669528273139819L;

    // ===========================================================
    // Fields
    // ===========================================================

    // ===========================================================
    // Constructors
    // ===========================================================

    public SmartList() {

    }

    public SmartList(final int pCapacity) {
        super(pCapacity);
    }

    // ===========================================================
    // Getter & Setter
    // ===========================================================

    // ===========================================================
    // Methods for/from SuperClass/Interfaces
    // ===========================================================

    public void addFirst(final T pItem) {
        this.add(0, pItem);
    }

    public void addLast(final T pItem) {
        this.add(this.size(), pItem);
    }

    public T getFirst() throws IndexOutOfBoundsException {
        return this.get(0);
    }

    public T getLast() throws IndexOutOfBoundsException {
        return this.get(this.size() - 1);
    }

    public T get(final IMatcher<T> pMatcher) {
        final int size = this.size();
        for (int i = 0; i < size; i++) {
            final T item = this.get(i);
            if (pMatcher.matches(item)) {
                return item;
            }
        }
        return null;
    }

    public T removeFirst() throws IndexOutOfBoundsException {
        return this.remove(0);
    }

    public T removeLast() throws IndexOutOfBoundsException {
        return this.remove(this.size() - 1);
    }

    /**
     * @param pItem              the item to remove.
     * @param pParameterCallable to be called with the removed item, if it was removed.
     */
    public boolean remove(final T pItem, final ParameterCallable<T> pParameterCallable) {
        final boolean removed = this.remove(pItem);
        if (removed) {
            pParameterCallable.call(pItem);
        }
        return removed;
    }

    public T remove(final IMatcher<T> pMatcher) {
        for (int i = 0; i < this.size(); i++) {
            if (pMatcher.matches(this.get(i))) {
                return this.remove(i);
            }
        }
        return null;
    }

    public T remove(final IMatcher<T> pMatcher, final ParameterCallable<T> pParameterCallable) {
        for (int i = this.size() - 1; i >= 0; i--) {
            if (pMatcher.matches(this.get(i))) {
                final T removed = this.remove(i);
                pParameterCallable.call(removed);
                return removed;
            }
        }
        return null;
    }

    public boolean removeAll(final IMatcher<T> pMatcher) {
        boolean result = false;
        for (int i = this.size() - 1; i >= 0; i--) {
            if (pMatcher.matches(this.get(i))) {
                this.remove(i);
                result = true;
            }
        }
        return result;
    }

    /**
     * @param pMatcher           to find the items.
     * @param pParameterCallable to be called with each matched item after it was removed.
     */
    public boolean removeAll(final IMatcher<T> pMatcher, final ParameterCallable<T> pParameterCallable) {
        boolean result = false;
        for (int i = this.size() - 1; i >= 0; i--) {
            if (pMatcher.matches(this.get(i))) {
                final T removed = this.remove(i);
                pParameterCallable.call(removed);
                result = true;
            }
        }
        return result;
    }

    public void clear(final ParameterCallable<T> pParameterCallable) {
        for (int i = this.size() - 1; i >= 0; i--) {
            final T removed = this.remove(i);
            pParameterCallable.call(removed);
        }
    }

    public int indexOf(final IMatcher<T> pMatcher) {
        final int size = this.size();
        for (int i = 0; i < size; i++) {
            final T item = this.get(i);
            if (pMatcher.matches(item)) {
                return i;
            }
        }
        return -1;
    }

    public int lastIndexOf(final IMatcher<T> pMatcher) {
        for (int i = this.size() - 1; i >= 0; i--) {
            final T item = this.get(i);
            if (pMatcher.matches(item)) {
                return i;
            }
        }
        return -1;
    }

    public ArrayList<T> query(final IMatcher<T> pMatcher) {
        return this.query(pMatcher, new ArrayList<T>());
    }

    public <L extends List<T>> L query(final IMatcher<T> pMatcher, final L pResult) {
        final int size = this.size();
        for (int i = 0; i < size; i++) {
            final T item = this.get(i);
            if (pMatcher.matches(item)) {
                pResult.add(item);
            }
        }

        return pResult;
    }

    public <S extends T> ArrayList<S> queryForSubclass(final IMatcher<T> pMatcher) {
        return this.queryForSubclass(pMatcher, new ArrayList<S>());
    }

    @SuppressWarnings("unchecked")
    public <L extends List<S>, S extends T> L queryForSubclass(final IMatcher<T> pMatcher, final L pResult) {
        final int size = this.size();
        for (int i = 0; i < size; i++) {
            final T item = this.get(i);
            if (pMatcher.matches(item)) {
                pResult.add((S) item);
            }
        }

        return pResult;
    }

    public void call(final ParameterCallable<T> pParameterCallable) {
        for (int i = this.size() - 1; i >= 0; i--) {
            final T item = this.get(i);
            pParameterCallable.call(item);
        }
    }

    public void call(final IMatcher<T> pMatcher, final ParameterCallable<T> pParameterCallable) {
        for (int i = this.size() - 1; i >= 0; i--) {
            final T item = this.get(i);
            if (pMatcher.matches(item)) {
                pParameterCallable.call(item);
            }
        }
    }

    // ===========================================================
    // Methods
    // ===========================================================

    // ===========================================================
    // Inner and Anonymous Classes
    // ===========================================================
}
